package za.ac.wits.elen7045.persistence.datasource;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import za.ac.wits.elen7045.persistence.Environment;
import za.ac.wits.elen7045.persistence.PersistentObject;
import za.ac.wits.elen7045.persistence.exception.DataSourceConnectionException;
import za.ac.wits.elen7045.persistence.exception.DataSourceLoadFailedException;
import za.ac.wits.elen7045.persistence.exception.DataSourceProcessException;
import za.ac.wits.elen7045.persistence.exception.DataSourceSaveFailedException;
import za.ac.wits.elen7045.persistence.exception.NotSupportedException;

public abstract class SQLDataSource<T extends PersistentObject> implements DataSource<T>{

	private Environment env;
	private int openRequests;
	
	protected Iterable<T> resultSetIterable = new Iterable<T>() {
		@Override
		public Iterator<T> iterator() {
			return new Iterator<T>() {
				@Override
				public boolean hasNext() {
					try {
						boolean hasNext = getResultSet().next(); 
						return hasNext;
					} catch (SQLException e) {
						e.printStackTrace();
						throw new DataSourceProcessException(e);
					}
				}
				@Override
				public T next() {
					try {
						return parseCurrentRowToObject(getResultSet());
					} catch (SQLException e) {
						e.printStackTrace();
						throw new DataSourceProcessException(e);
					}
				}
				@Override
				public void remove() {
					throw new NotSupportedException();
				}
			};
		}
	};

	protected abstract Connection getConnection() throws SQLException;
	protected abstract ResultSet getResultSet();
	protected abstract void setResultSet(ResultSet resultSet);
	protected abstract T parseCurrentRowToObject(ResultSet resultSet) throws SQLException;
	protected abstract DataSourceRowEntry parseObjectToDataSourceRowEntry(T object) throws SQLException;

	protected Environment getEnvironment() {
		return env;
	}
	
	@Override
	public void setEnvironment(Environment env) {
		this.env = env;
	}
	
	@Override
	public synchronized void open() {
		if (openRequests == 0) {
			try {
				Connection connection = getConnection(); 
				openRequests++;
				connection.setAutoCommit(false);
			} catch (SQLException e) {
				throw new DataSourceLoadFailedException(e);
			}
		} else {
			openRequests++;
		}
	}

	@Override
	public void commit() {
		ensureConnectionOpen();
		try {
			getConnection().commit();
			if (getResultSet() != null) {
				getResultSet().close();
			}
		} catch (SQLException e) {
			throw new DataSourceSaveFailedException(e);
		}
	}

	@Override
	public void rollback() {
		ensureConnectionOpen();
		try {
			getConnection().rollback();
			if (getResultSet() != null) {
				getResultSet().close();
			}
		} catch (SQLException e) {
			throw new DataSourceSaveFailedException(e);
		}
	}

	@Override
	public synchronized void close() {
		if (openRequests == 0) {
			return;
		} else if (openRequests == 1) {
			try {
				getConnection().setAutoCommit(true);
				getConnection().close();
				openRequests--;
			} catch (SQLException e) {
				throw new DataSourceLoadFailedException(e);
			}
		} else {
			openRequests--;
		}
	}
	
	@Override
	public boolean isOpen() {
		return openRequests > 0;
	}
	
	@Override
	public Iterable<T> retrieve(String query) {
		ensureConnectionOpen();
		try {
			setResultSet(getConnection().createStatement().executeQuery(query));
			return resultSetIterable;
		} catch (SQLException e) {
			e.printStackTrace();
			throw new DataSourceProcessException(e);
		}
	}
	
	@Override
	public void submit(String query) {
		ensureConnectionOpen();
		try {
			getConnection().createStatement().executeUpdate(query);
		} catch (SQLException e) {
			e.printStackTrace();
			throw new DataSourceProcessException(e);
		}
	}
	
	/* Utilities */
	
	protected void ensureConnectionOpen() {
		if (!isOpen()) {
			throw new DataSourceConnectionException("Connection expected to be open but found to be closed!");
		}
	}
	
	protected void ensureConnectionClosed() {
		if (isOpen()) {
			throw new DataSourceConnectionException("Connection expected to be closed but found to be already established!");
		}
	}
	
}
